// Copyright 2013 The golog2bq Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build appengine

// The log2bq package is very much an early alpha at this point.  You can use
// this package to export GAE logs in CSV format to a gziped CSV file download,
// BigQuery via Google Cloud Storage or directly to BigQuery.
//
// If you are using this package as an alpha/beta tester, then please email
// gavaletz@gmail.com and let me know.  This way I can notify you of any changes
// that may break the API or if there is a bug that may cause you trouble,
//
// Please use the issue tracker to report any bugs.
//
// Example usage:
//	func init() {
//		InitLogger("gae_logs", "123456789101",
//			DefaultWriteDisposition, "app-logs")
//		http.HandleFunc("/admin/logs.csv.gz", Log2CSV)
//		http.HandleFunc("/admin/log2bq", Log2bqWorker)
//		http.HandleFunc("/admin/log2gs", Log2gsWorker)
//	}
//
package log2bq

import (
	"appengine"
	"appengine/log"
	"encoding/csv"
	"errors"
	"io"
	"strconv"
	"strings"
	"time"
)

const DefaultWriteDisposition = "WRITE_APPEND"

var (
	ErrNilQuery        = errors.New("log query is nil")
	ErrInvalidGSBucket = errors.New("invalid GS bucket name")
	ErrNilLogger       = errors.New("call InitLogger before accepting requests")
	globalLogger       *Logger
)

func logRecord2CSV(r *log.Record) []string {
	return []string{
		r.AppID,
		r.VersionID,
		string(r.RequestID),
		r.IP,
		r.Nickname,
		r.AppEngineRelease,
		bqTime(r.StartTime),
		bqTime(r.EndTime),
		strconv.FormatInt(r.Latency.Nanoseconds(), 10),
		strconv.FormatInt(r.MCycles, 10),
		r.Method,
		r.Resource,
		r.HTTPVersion,
		strconv.FormatInt(int64(r.Status), 10),
		strconv.FormatInt(r.ResponseSize, 10),
		r.Referrer,
		r.UserAgent,
		r.URLMapEntry,
		r.Combined,
		r.Host,
		strconv.FormatFloat(r.Cost, 'G', -1, 64),
		r.TaskQueueName,
		r.TaskName,
		strconv.FormatBool(r.WasLoadingRequest),
		strconv.FormatInt(r.PendingTime.Nanoseconds(), 10),
		strconv.FormatBool(r.Finished),
	}
}

// fixTableName makes a valid GAE app name a valid BigQuery table name.
//
// GAE Aapplication Identifiers must be between 6 and 30 characters. Lowercase
// letters, digits, and hyphens are acceptable characters. Leading and
// trailing hyphens are prohibited.
//
// BigQuery names are any combination of uppercase and/or lowercase letters (A-Z,
// a-z), digits (0-9) and underscores, but no spaces. The first character must
// be a letter.
func fixTableName(s string) string {
	return strings.Replace(s, "-", "", -1)
}

// bqTime formats a Go time.Time value so that BigQuery will recognize it as a
// valid timestamp.
func bqTime(t time.Time) string {
	return strconv.FormatFloat(
		float64(t.UnixNano())/1000000000.0,
		'G', -1, 64)
}

func gsNameFix(n string) string {
	return strings.Replace(n, "/gs/", "gs://", 1)
}

func log2csv(w io.Writer, c appengine.Context, q *log.Query) error {
	if q == nil {
		return ErrNilQuery
	}
	cw := csv.NewWriter(w)
	res := q.Run(c)
	var r *log.Record
	var err error
	for {
		r, err = res.Next()
		if err == log.Done {
			break
		}
		if err != nil {
			return err
		}
		err = cw.Write(logRecord2CSV(r))
		if err != nil {
			return err
		}
	}
	cw.Flush()
	return nil
}

func newLogQuery(s, e string, v []string) *log.Query {
	//TODO(gavaletz) allow specifying Offset
	start, _ := strconv.ParseInt(s, 0, 64)
	end, _ := strconv.ParseInt(e, 0, 64)
	q := &log.Query{
		Incomplete:    false,
		AppLogs:       false,
		ApplyMinLevel: false,
	}
	if start > 0 {
		q.StartTime = time.Unix(start, 0)
	}
	if end > 0 {
		q.EndTime = time.Unix(end, 0)
	}
	if v != nil && len(v) > 0 && v[0] != "" {
		q.Versions = v
	}
	return q
}
